#include "Zipper.h"
#include <utility>


Zipper::Zipper(PComunicator::PFileInfo theArchiv)
{
	myScheme = NULL;
	myZipFile = NULL;


	if(theArchiv.GetFileType() != "zip" && !theArchiv.GetFileType().empty())
	{
		throw std::exception();
	}

	myZipFile = new zipios::ZipFile(theArchiv.GetFile());
	BuildScheme(theArchiv.GetName());
}


Zipper::~Zipper(void)
{
	delete myScheme;
	myZipFile->close();
	delete myZipFile;  //change this
}

bool Zipper::Extract(std::string theFileName, PComunicator::PFileInfo **pOut)
{
	zipios::ConstEntries entries = myZipFile->entries();
	zipios::ConstEntries::iterator iter;

	for(iter = entries.begin(); iter != entries.end(); iter++)
	{
		if( (*iter)->isDirectory() )
			continue;
		else
		{
			std::size_t pos = (*iter)->getFileName().rfind(theFileName);
			if( pos != std::string::npos )
			{
				std::string fullFileName = (*iter)->getFileName();
				zipios::ConstEntryPointer entri = myZipFile->getEntry(fullFileName,zipios::FileCollection::ZIP_IGNORE);
				std::istream *pIstream = myZipFile->getInputStream(entri);

				(*pOut) = new PComunicator::PFileInfo(fullFileName,pIstream);
				delete pIstream;
				return true;
			}
		}
	}

	return false;
}

bool Zipper::ExtractNoType(std::string theFileName, PComunicator::PFileInfo **pOut)
{
	zipios::ConstEntries entries = myZipFile->entries();
	zipios::ConstEntries::iterator iter;

	for(iter = entries.begin(); iter != entries.end(); iter++)
	{
		if( (*iter)->isDirectory() )
			continue;
		else
		{
			if( filename_without_type((*iter)->getFileName()) == theFileName )
			{
				std::string fullFileName = (*iter)->getFileName();
				zipios::ConstEntryPointer entri = myZipFile->getEntry(fullFileName,zipios::FileCollection::ZIP_IGNORE);
				std::istream *pIstream = myZipFile->getInputStream(entri);

				(*pOut) = new PComunicator::PFileInfo(fullFileName,pIstream);
				delete pIstream;
				return true;
			}
		}
	}

	return false;
}

const Validator::Scheme& Zipper::GetScheme(void) const
{
	return (*myScheme);
}

//**************************************************************//
//						PRIVATE METHODS							//
//**************************************************************//

void Zipper::BuildScheme(std::string theName)
{
	myScheme = new Validator::Scheme(theName,"zip");

	std::vector<Validator::MetaDirectory> aDirColl;
	std::vector<std::pair<Validator::MetaFile,std::string> > aFileColl;

	zipios::ConstEntries entries = myZipFile->entries();
	zipios::ConstEntries::iterator iter;

	for(iter = entries.begin(); iter != entries.end(); iter++)
	{
		if( (*iter)->isDirectory() )
		{
			ParseDirectory((*iter)->getName(),aDirColl);
		}
		else
		{
			ParseFile((*iter)->getName(),aFileColl);
		}
	}

	for(unsigned int i=0; i < aDirColl.size(); i++)
	{
		myScheme->Add(aDirColl[i]);
	}

	for(unsigned int i=0; i < aFileColl.size(); i++)
	{
		for(std::size_t x=0; x < myScheme->GetDirectories().size(); x++)
		{
			if(AddFile(myScheme->GetDirectories()[x],aFileColl[i].second,aFileColl[i].first))
				break;
		}
	}
}

bool Zipper::AddFile(Validator::MetaDirectory &theDir,std::string theDirName,Validator::MetaFile theFile)
{
	if(theDir.GetName() == theDirName)
	{
		theDir.Add(theFile);
		return true;
	}

	for(std::size_t i=0; i < theDir.GetDirectories().size(); i++)
	{
		AddFile(theDir.GetDirectories()[i],theDirName,theFile);
	}
	
	return false;
}

void Zipper::ParseDirectory(std::string theInput,std::vector<Validator::MetaDirectory> &theDirColl)
{
	std::vector<std::string> aResult = Tools::Split(theInput,"/");

	//skontrolujeme ci mame v root-e archivu adresar ktory ide do rootu
	bool isInCollection = false;
	unsigned int index = 0;
	for(unsigned int i=0; i < theDirColl.size(); i++)
	{
		if(theDirColl[i].GetName() == aResult[0])
		{
			isInCollection = true;
			index = i;
			break;
		}
	}

	if(!isInCollection)
	{
		Validator::MetaDirectory aDirectory(aResult[0],std::vector<Validator::MetaFile>(),std::vector<Validator::MetaDirectory>(),false);
		theDirColl.push_back(aDirectory);
		aResult.erase(aResult.begin());
		AddDirToDir(theDirColl[theDirColl.size()-1],aResult);
	}
	else
	{
		aResult.erase(aResult.begin());
		AddDirToDir(theDirColl[index],aResult);
	}
}

void Zipper::AddDirToDir(Validator::MetaDirectory &theDir,std::vector<std::string> theDirNameColl)
{
	bool isInCollection = false;
	unsigned int index = 0;

	if(theDirNameColl.empty())
		return;

	for(unsigned int i=0; i < theDir.GetDirectories().size(); i++)
	{
		if(theDir.GetDirectories()[i].GetName() == theDirNameColl[0])
		{
			isInCollection = true;
			index = i;
		}
	}

	if(!isInCollection)
	{
		Validator::MetaDirectory aDir(theDirNameColl[0],std::vector<Validator::MetaFile>(),std::vector<Validator::MetaDirectory>(),false);
		theDir.Add(aDir);
		theDirNameColl.erase(theDirNameColl.begin());
		AddDirToDir(theDir.GetDirectories()[theDir.GetDirectories().size()-1],theDirNameColl);
	}
	else
	{
		theDirNameColl.erase(theDirNameColl.begin());
		AddDirToDir(theDir.GetDirectories()[index],theDirNameColl);
	}
}


void Zipper::ParseFile(std::string theInput,std::vector<std::pair<Validator::MetaFile,std::string> > &theFileColl)
{
	std::vector<std::string> aResult = Tools::Split(theInput,"/");
	char aType[4];

	if(aResult.empty())		//jedna sa o subor v korenovom adresary archivu
	{
		GetFileType(theInput,aType);
		Validator::MetaFile aFile(theInput,aType,true);
		theFileColl.push_back(std::make_pair(aFile,"root"));
	}
	else	//subor sa nachadza v nejakom adresary
	{
		std::string theName = aResult[aResult.size()-1];
		GetFileType(theName,aType);
		Validator::MetaFile aFile(theName,aType,true);

		theFileColl.push_back(std::make_pair(aFile,aResult[aResult.size()-2]));
	}
}


void Zipper::GetFileType(std::string theInput,char *theOutput)
{
	std::pair<std::string,std::string> aPair = Tools::Separate(theInput,'.',false);
	strncpy(theOutput,aPair.second.c_str(),3);
	theOutput[3] = '\0'; //nullterminacia
}


std::string Zipper::filename_without_type(std::string filename)
{
	std::pair<std::string,std::string> aPair = Tools::Separate(filename,'.',false);
	return aPair.first;
}